package com.qd.core.aspect.operarorlog;

import com.cdqidi.util.AnnotationUtil;
import com.cdqidi.util.IpHelper;
import com.cdqidi.util.ServletUtil;
import com.cdqidi.util.SpringContextHolder;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.qd.common.sys.annotation.OperatorInfo;
import com.qd.common.sys.constant.Constants;
import com.qd.common.sys.domain.entity.log.OperatorLogDTO;
import com.qd.common.sys.domain.event.OperatorDTO;
import com.qd.common.sys.domain.login.LoginUser;
import com.qd.common.sys.event.OperatorLogEvent;
import com.qd.core.dblog.LogProcess;
import com.qd.system.service.person.AuthTokenService;
import com.qd.system.config.AdminConfig;
import com.qd.system.model.DataLog;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.time.LocalDateTime;
import java.util.List;

/**
 * 记录操作日志
 *
 * @author sjk
 */
@Aspect
@Component
@Slf4j
@RequiredArgsConstructor
@ConditionalOnProperty(prefix = "admin-config", name = "isOperator", havingValue = "true")
public class LogAspect {
    private final AuthTokenService authTokenService;
    private final ObjectMapper objectMapper;
    private final AdminConfig adminConfig;

    /**
     * 定义切入点
     */
    @Pointcut("@annotation(com.qd.common.sys.annotation.OperatorInfo)")
    public void logPointCut() {
    }

    @Before(value = "logPointCut()")
    public void doBefore(JoinPoint joinPoint) throws JsonProcessingException {
        final OperatorInfo controllerOperatorInfo = AnnotationUtil.getMethodAnnotation(joinPoint, OperatorInfo.class);
        if (null == controllerOperatorInfo) {
            return;
        }
        //获取目标方法的参数信息
        Object[] obj = null;
        if (controllerOperatorInfo.isSaveRequestData()) {
            obj = joinPoint.getArgs();
        }
        final long startTime = System.currentTimeMillis();
        final OperatorAopLog operatorAopLog = new OperatorAopLog();
        if (null != obj) {
            operatorAopLog.setParams(argsArrayToString(obj));
        }
        // 设置方法名称
        final String className = joinPoint.getTarget().getClass().getName();
        final String methodName = joinPoint.getSignature().getName();
        operatorAopLog.setMethod(className + "." + methodName + "()");
        operatorAopLog.setStartTime(startTime);
        operatorAopLog.setTitle(controllerOperatorInfo.title());
        operatorAopLog.setOperatorType(controllerOperatorInfo.operator());
        operatorAopLog.setAuditLog(controllerOperatorInfo.isAuditLog());
        OperatorLogManager.setOperatorAopLog(operatorAopLog);
    }

    /**
     * 处理完请求后执行
     *
     * @param joinPoint 切点
     */
    @AfterReturning(pointcut = "logPointCut()", returning = "jsonResult")
    public void doAfterReturning(JoinPoint joinPoint, Object jsonResult) {
        handleLog(joinPoint, null, jsonResult);
    }

    /**
     * 拦截异常操作
     *
     * @param joinPoint 切点
     * @param e         异常
     */
    @AfterThrowing(value = "logPointCut()", throwing = "e")
    public void doAfterThrowing(JoinPoint joinPoint, Throwable e) {
        handleLog(joinPoint, e, null);
    }

    /**
     * 最终结束
     */
    @After(value = "logPointCut()")
    public void doAfter() {
        if (log.isDebugEnabled()) {
            log.debug("回收资源");
        }
        if (adminConfig.isAuditLog()) {
            //记录数据日志
            saveAuditLog();
        }
        OperatorLogManager.clear();
    }

    protected void handleLog(final JoinPoint joinPoint, final Throwable e, Object jsonResult) {
        try {
            final OperatorAopLog operatorAopLog = OperatorLogManager.getOperatorAopLog();
            final OperatorDTO operatorDTO = new OperatorDTO();
            operatorDTO.setStartTime(operatorAopLog.getStartTime());
            if (null != operatorAopLog.getParams()) {
                operatorDTO.setRequestParam(operatorAopLog.getParams());
            }
            // 获得注解
            final OperatorInfo controllerOperatorInfo = AnnotationUtil.getMethodAnnotation(joinPoint, OperatorInfo.class);
            if (null == controllerOperatorInfo) {
                return;
            }
            // 获取当前的用户
            final LoginUser loginUser = authTokenService.getLoginUser(ServletUtil.getRequest());
            // *========数据库日志=========*//
            OperatorLogDTO operatorLog = new OperatorLogDTO();
            operatorLog.setStatus(Constants.LOGIN_SUCCESS);
            // 请求的地址
            String ip = IpHelper.getIpAddress(ServletUtil.getRequest());
            operatorLog.setUserIp(ip);
            operatorLog.setUrl(ServletUtil.getRequest().getRequestURI());
            if (null != loginUser) {
                operatorLog.setRemote(loginUser.getRemote());
                if (null != loginUser.getUser()) {
                    operatorLog.setUserName(loginUser.getUser().getPersonName());
                    operatorLog.setUserId(loginUser.getUser().getPersonId());
                    if (StringUtils.hasLength(loginUser.getUser().getOrgId())) {
                        operatorLog.setOrgId(loginUser.getUser().getOrgId());
                    }
                    if (StringUtils.hasLength(loginUser.getUser().getOrgIdDisplay())) {
                        operatorLog.setOrgName(loginUser.getUser().getOrgIdDisplay());
                    }
                }
            }
            if (e != null) {
                operatorLog.setStatus(Constants.LOGIN_FAIL);
                operatorLog.setErrorMsg(e.getMessage());
            }
            operatorLog.setMethod(operatorAopLog.getMethod());
            // 设置请求方式
            operatorLog.setRequestMethod(ServletUtil.getRequest().getMethod());
            // 处理设置注解上的参数
            buildControllerMethodDescription(controllerOperatorInfo, operatorLog);
            if (controllerOperatorInfo.isSaveResponseData() && null != jsonResult) {
                operatorDTO.setResponseParam(jsonResult);
            }
            final long endTime = System.currentTimeMillis();
            operatorDTO.setEndTime(endTime);
            operatorDTO.setOperatorLog(operatorLog);
            SpringContextHolder.publishEvent(new OperatorLogEvent(operatorDTO));
        } catch (Exception exp) {
            exp.printStackTrace();
            log.error("异常信息:{}", exp.getMessage());
        }
    }

    /**
     * 获取注解中对方法的描述信息 用于Controller层注解
     *
     * @param controllerOperatorInfo 注解
     * @param operatorLog            操作日志
     */
    public void buildControllerMethodDescription(OperatorInfo controllerOperatorInfo, OperatorLogDTO operatorLog) {
        // 设置action动作
        operatorLog.setOperatorType(controllerOperatorInfo.operator().name());
        // 设置标题
        operatorLog.setTitle(controllerOperatorInfo.title());
    }

    /**
     * 参数拼装
     */
    private String argsArrayToString(Object[] paramsArray) throws JsonProcessingException {
//        final long start = System.currentTimeMillis();
//        try {
        if (null == paramsArray || paramsArray.length < 1) {
            return null;
        }
        final StringBuilder str = new StringBuilder();
        for (Object obj : paramsArray) {
            if (!isFilterObject(obj)) {
                str.append(objectMapper.writeValueAsString(obj));
            }
        }
        return str.toString();
//        } finally {
//            final long end = System.currentTimeMillis();
//            log.info("处理request参数耗时: " + (end - start));
//        }
    }

    /**
     * 判断是否需要过滤的对象。
     *
     * @param o 对象信息。
     * @return 如果是需要过滤的对象，则返回true；否则返回false。
     */
    public boolean isFilterObject(final Object o) {
        return o instanceof MultipartFile || o instanceof HttpServletRequest || o instanceof HttpServletResponse;
    }

    private void saveAuditLog() {
        List<DataLog> dataLogList = OperatorLogManager.getOperatorAopLog().getDataLogList();
        if (!dataLogList.isEmpty()) {
            String ip = IpHelper.getIpAddress(ServletUtil.getRequest());
            String requestURI = ServletUtil.getRequest().getRequestURI();
            String reqMethod = ServletUtil.getRequest().getMethod();
            OperatorAopLog operatorAopLog = OperatorLogManager.getOperatorAopLog();
            // 获取当前的用户
            final LoginUser loginUser = authTokenService.getLoginUser(ServletUtil.getRequest());
            if (!dataLogList.isEmpty()) {
                LocalDateTime now = LocalDateTime.now();
                dataLogList.parallelStream().forEach(dataLog -> {
                    dataLog.setMethod(operatorAopLog.getMethod());
                    dataLog.setOperatorType(operatorAopLog.getOperatorType().name());
                    dataLog.setTitle(operatorAopLog.getTitle());
                    dataLog.setOperatorTime(now);
                    dataLog.setRequestMethod(reqMethod);
                    dataLog.setUserIp(ip);
                    dataLog.setUrl(requestURI);
                    if (null != dataLog.getContentObj()) {
                        try {
                            dataLog.setContent(objectMapper.writeValueAsString(dataLog.getContentObj()));
                        } catch (JsonProcessingException ignored) {
                        }
                    }
                    buildUser(loginUser, dataLog);
                });
                LogProcess.getInstance().addDataLogs(dataLogList);
                dataLogList.clear();
            }
        }
    }

    private void buildUser(LoginUser loginUser, DataLog dataLog) {
        if (null != loginUser) {
            dataLog.setRemote(loginUser.getRemote());
            if (null != loginUser.getUser()) {
                dataLog.setUserName(loginUser.getUser().getPersonName());
                dataLog.setUserId(loginUser.getUser().getPersonId());
                if (StringUtils.hasLength(loginUser.getUser().getOrgId())) {
                    dataLog.setOrgId(loginUser.getUser().getOrgId());
                }
                if (StringUtils.hasLength(loginUser.getUser().getOrgIdDisplay())) {
                    dataLog.setOrgName(loginUser.getUser().getOrgIdDisplay());
                }
            }
        }
    }
}
